/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.extension.db.internal.operation;

import static java.lang.Math.min;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import org.mule.extension.db.api.StatementResult;
import org.mule.extension.db.api.exception.connection.BadSqlSyntaxException;
import org.mule.extension.db.api.param.ParameterType;
import org.mule.extension.db.api.param.ParameterizedStatementDefinition;
import org.mule.extension.db.api.param.QueryDefinition;
import org.mule.extension.db.api.param.QuerySettings;
import org.mule.extension.db.internal.DbConnector;
import org.mule.extension.db.internal.domain.autogeneratedkey.AutoGenerateKeysStrategy;
import org.mule.extension.db.internal.domain.autogeneratedkey.ColumnIndexAutoGenerateKeysStrategy;
import org.mule.extension.db.internal.domain.autogeneratedkey.ColumnNameAutoGenerateKeysStrategy;
import org.mule.extension.db.internal.domain.autogeneratedkey.DefaultAutoGenerateKeysStrategy;
import org.mule.extension.db.internal.domain.autogeneratedkey.NoAutoGenerateKeysStrategy;
import org.mule.extension.db.internal.domain.connection.DbConnection;
import org.mule.extension.db.internal.domain.executor.UpdateExecutor;
import org.mule.extension.db.internal.domain.param.QueryParam;
import org.mule.extension.db.internal.domain.query.Query;
import org.mule.extension.db.internal.domain.query.QueryTemplate;
import org.mule.extension.db.internal.domain.query.QueryType;
import org.mule.extension.db.internal.domain.statement.QueryStatementFactory;
import org.mule.extension.db.internal.resolver.query.ParameterizedQueryResolver;
import org.mule.extension.db.internal.resolver.query.QueryResolver;
import org.mule.runtime.extension.api.runtime.streaming.StreamingHelper;

import java.sql.SQLException;
import java.util.List;
import java.util.Set;

/**
 * Base class with common functionality for Database operations
 *
 * @since 1.0
 */
public abstract class BaseDbOperations {

  public static final int DEFAULT_FETCH_SIZE = 10;
  protected static final String QUERY_GROUP = "Query";
  protected static final String QUERY_SETTINGS = "Query Settings";

  protected final QueryResolver<ParameterizedStatementDefinition> queryResolver = new ParameterizedQueryResolver<>();

  protected QueryStatementFactory getStatementFactory(QuerySettings settings) {

    QueryStatementFactory statementFactory = new QueryStatementFactory();

    final int maxRows = settings.getMaxRows() != null ? settings.getMaxRows() : 0;
    int fetchSize = getFetchSize(settings);

    if (maxRows > 0) {
      statementFactory.setMaxRows(maxRows);
      fetchSize = min(fetchSize, maxRows);
    }

    statementFactory.setFetchSize(fetchSize);
    statementFactory.setQueryTimeout(new Long(settings.getQueryTimeoutUnit().toSeconds(settings.getQueryTimeout())).intValue());

    return statementFactory;
  }

  protected StatementResult executeUpdate(QueryDefinition query,
                                          AutoGenerateKeysAttributes autoGenerateKeysAttributes,
                                          DbConnection connection,
                                          Query resolvedQuery)
      throws SQLException {
    QueryStatementFactory statementFactory = getStatementFactory(query);
    return (StatementResult) new UpdateExecutor(statementFactory)
        .execute(connection, resolvedQuery, getAutoGeneratedKeysStrategy(autoGenerateKeysAttributes));
  }

  protected Query resolveQuery(QueryDefinition query,
                               DbConnector connector,
                               DbConnection connection,
                               StreamingHelper streamingHelper,
                               QueryType... validTypes) {

    final Query resolvedQuery = queryResolver.resolve(query, connector, connection, streamingHelper);
    validateQueryType(resolvedQuery.getQueryTemplate(), asList(validTypes));
    validateNoParameterTypeIsUnused(resolvedQuery, query.getParameterTypes());
    return resolvedQuery;
  }

  protected void validateQueryType(QueryTemplate queryTemplate, List<QueryType> validTypes) {
    if (validTypes == null || !validTypes.contains(queryTemplate.getType())) {
      String typeList = validTypes.stream().map(QueryType::name).collect(joining(", "));
      throw new BadSqlSyntaxException(format("Query type must be one of [%s] but query '%s' is of type '%s'",
                                             typeList, queryTemplate.getSqlText(),
                                             queryTemplate.getType()));
    }
  }


  protected void validateNoParameterTypeIsUnused(Query query, List<ParameterType> parameterTypes) {
    Set<String> params = query.getQueryTemplate().getParams().stream().map(QueryParam::getName).collect(toSet());
    Set<String> unusedTypes =
        parameterTypes.stream().map(type -> type.getKey()).filter(type -> !params.contains(type)).collect(toSet());

    if (!unusedTypes.isEmpty()) {
      String msg = unusedTypes.stream()
          .map(s -> new StringBuilder().append("'").append(s).append("'").toString())
          .collect(joining(", "));
      throw new IllegalArgumentException(format("Query defines parameters [%s] but they aren't present in the query", msg));
    }
  }

  protected AutoGenerateKeysStrategy getAutoGeneratedKeysStrategy(AutoGenerateKeysAttributes keyAttributes) {

    if (keyAttributes == null) {
      return new NoAutoGenerateKeysStrategy();
    }

    if (keyAttributes.isAutoGenerateKeys()) {

      final List<Integer> columnIndexes = keyAttributes.getAutoGeneratedKeysColumnIndexes();
      final List<String> columnNames = keyAttributes.getAutoGeneratedKeysColumnNames();

      if (!isEmpty(columnIndexes)) {
        int[] indexes = new int[columnIndexes.size()];
        int i = 0;
        for (int index : columnIndexes) {
          indexes[i++] = index;
        }
        return new ColumnIndexAutoGenerateKeysStrategy(indexes);
      } else if (!isEmpty(columnNames)) {
        return new ColumnNameAutoGenerateKeysStrategy(columnNames.stream().toArray(String[]::new));
      } else {
        return new DefaultAutoGenerateKeysStrategy();
      }
    } else {
      return new NoAutoGenerateKeysStrategy();
    }
  }

  protected int getFetchSize(QuerySettings settings) {
    Integer fetchSize = settings.getFetchSize();
    return fetchSize != null ? fetchSize : DEFAULT_FETCH_SIZE;
  }
}
